﻿//************************************************************************************************************
// CoNatural.Threading.dll
// Author: Roger Torres
//************************************************************************************************************
// You can use, modify, and distribute the source code and executable programs based on the source code.
// This source code is provided "as is" and without warranties as to performance or merchantability. 
// The author and/or distributors of this source code may have made statements about this source code. 
// Any such statements do not constitute warranties and shall not be relied on by the user in deciding 
// whether to use this source code. This source code is provided without any express or implied warranties 
// whatsoever. Because of the diversity of conditions and hardware under which this source code may be used, 
// no warranty of fitness for a particular purpose is offered. The user is advised to test the source code 
// thoroughly before relying on it. The user must assume the entire risk of using the source code.
//************************************************************************************************************

using System;
using System.Collections.Generic;
using System.Threading;

namespace CoNatural.Threading.Async {
   /// <summary>
   /// Executes multiple asynchronous actions (branches) in parallel.
   /// </summary>
   public class Parallel : AsyncAction {
      /// <summary>
      /// The branches of the parallel action.
      /// </summary>
      public AsyncAction[] Branches { get; private set; }

      /// <summary>
      /// Constructor.
      /// </summary>
      /// <param name="name">The name of the action.</param>
      /// <param name="raiseEvents">True to raise events, False otherwise.</param>
      /// <param name="branches">The branches.</param>
      public Parallel(string name, bool raiseEvents, params AsyncAction[] branches)
         : base(name, raiseEvents) {
         Branches = branches;
      }

      /// <summary>
      /// Overrides the Start method of the base class since each
      /// parallel action executes in a dedicated ThreadPool thread.
      /// </summary>
      /// <param name="context"></param>
      public override void Start(AsyncContext context) {
         context.OnStart(this);
         BeginInvoke(this, () => context.OnCompleted());
      }

      /// <summary>
      /// Invokes branches and wait for all to complete before resuming.
      /// </summary>
      /// <param name="source">The source async action.</param>
      /// <param name="nextAction">The delegate to continue.</param>
      internal override void BeginInvoke(AsyncAction source, Action nextAction) {
         Context = source.Context;

         // parallel actions start in their own managed threads because they must wait
         // for all branches to complete before resuming
         ThreadPool.QueueUserWorkItem(
            (state) => {
               // store the number of branches that will be started
               long activeBranches = Branches.Length;
               ManualResetEvent yield = new ManualResetEvent(false);

               foreach (AsyncAction branch in Branches)
                  branch.BeginInvoke(
                     this,
                     () => {
                        // this branch is done, decrement counter
                        long pending = Interlocked.Decrement(ref activeBranches);

                        // release parallel action when all branches are completed
                        if (pending == 0)
                           yield.Set();
                     }
                  );

               // wait for all branches to complete
               yield.WaitOne();

               // we are done
               Complete(false, null);
               nextAction();
            }
         );
      }
   }
}
